package pre;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import it.unisa.dia.gas.jpbc.Element;
import it.unisa.dia.gas.jpbc.Field;
import it.unisa.dia.gas.jpbc.Pairing;
import it.unisa.dia.gas.plaf.jpbc.pairing.PairingFactory;
import it.unisa.dia.gas.plaf.jpbc.pairing.a.TypeACurveGenerator;



public class PRE {


    Pairing pairing ;
    Field G1;
    Field GT;
    Field Zq;
    Element g,g1,h1,h2,h3,h4;
    Element one ;

    public Field getG1() {
        return G1;
    }

    public Field getGT() {
        return GT;
    }

    public Field getZq() {
        return Zq;
    }

    public Element hash_to_Zq(byte[] m) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(m);
//        System.out.println("---------++++++++++++++++++++++-----------");
//        System.out.println(Zq.newElementFromBytes(md.digest()));
//        System.out.println("-----------++++++++++++++++++++++++---------");
//        System.out.println(Zq.newElementFromBytes(md.digest()).invert());
        Element temp1 = Zq.newElementFromBytes(md.digest());
        Element temp2 = Zq.newElementFromBytes(md.digest()).invert();
//        System.out.println(temp1.mulZn(temp2));
        return  Zq.newElementFromBytes(md.digest()).invert();
    }

    public byte[] or(byte[] as,byte[] bs) {
//        System.out.println(as.length);
//        System.out.println(bs.length);
        byte[] ret=new byte[as.length];
        for(int i=0;i<as.length;i++) {
            ret[i]=(byte) (as[i]^bs[i]);
        }

        return ret;
    }

    public byte[] a_b(byte[] as,byte[] bs){
        int length = as.length+bs.length;
        byte[] ret=new byte[length];
        for(int i=0;i<as.length;i++) {
            ret[i]=as[i];
        }
        for(int i=as.length;i<length;i++){
            ret[i]=bs[i-as.length];
        }
        return ret;
    }

    public void setup(){
//        this.pairing = PairingFactory.getPairing(new TypeACurveGenerator(security_parameter * 5 / 32, security_parameter / 2).generate());
        this.pairing = PairingFactory.getPairing("resource/a.properties");
        this.G1 = pairing.getG1();
        this.GT = pairing.getGT();
        this.Zq = pairing.getZr();
        this.g =  G1.newRandomElement();
        this.g1 = G1.newRandomElement();
        this.h1 = G1.newRandomElement();
        this.h2 = G1.newRandomElement();
        this.h3 =  G1.newRandomElement();
        this.h4 =  G1.newRandomElement();
        this.one =  this.G1.newOneElement();
    }

    public Map<String,Element> keyGen(){
        Element  x =  Zq.newRandomElement().invert();
        Element pk = g.duplicate().powZn(x);
        Map<String,Element> keyPair = new HashMap<>();
        keyPair.put("pk",pk);
        keyPair.put("sk", x);
        return keyPair;
    }

    public List<Element> encrypt(Element pk, Element m) throws NoSuchAlgorithmException {
        List<Element> ciphertext = new ArrayList<>();

        Element r =  Zq.newRandomElement().invert();

        Element C11 = g.duplicate().powZn(r);

        Element C12 = m.duplicate().mul((pairing.pairing(g1,pk)).duplicate().powZn(r));

        Element C13 = ((( h1.duplicate().powZn(hash_to_Zq(C11.toBytes())) ).mul(h2.duplicate().powZn(hash_to_Zq(a_b(C11.toBytes(),C12.toBytes()))))).mul(h3)).powZn(r);

        ciphertext.add(C11);
        ciphertext.add(C12);
//        System.out.println("c13");
//        System.out.println(C13);
        ciphertext.add(C13);
        return ciphertext;
    }

    public List<Element> rkgen(Element ski, Element pkj) throws NoSuchAlgorithmException {
        Element ri = Zq.newRandomElement().invert();
        List<Element> rk_i_j = new ArrayList<>();
        Element rki1 = g.duplicate().powZn(ri);
        Element rki2 = (one.duplicate().div(g1.duplicate().powZn(ski))).mul(pkj.duplicate().powZn(ri));
        Element rki3 = pairing.pairing(pkj,pkj).powZn(ri);
        Element rki4 = (h2.duplicate().powZn(hash_to_Zq(a_b(a_b(rki1.toBytes(),rki2.toBytes()),rki3.toBytes()))).mul(h4)).powZn(ri);
        rk_i_j.add(rki1);
        rk_i_j.add(rki2);
        rk_i_j.add(rki3);
        rk_i_j.add(rki4);
        return rk_i_j;
    }

    public List<Element> reEncrypt(List<Element> rk_i_j, List<Element> ciphertext , Element skpi) throws NoSuchAlgorithmException {
        Element c11 = ciphertext.get(0);
        Element c12 = ciphertext.get(1);
        Element c13 = ciphertext.get(2);

        Element rki1 = rk_i_j.get(0);
        Element rki2 = rk_i_j.get(1);
        Element rki3 = rk_i_j.get(2);
        Element rki4 = rk_i_j.get(3);

        Element temp1 = pairing.pairing(g,c13);
        Element hash_value = hash_to_Zq(a_b(c11.toBytes(),c12.toBytes()));
        Element temp2 = pairing.pairing(c11,(h1.duplicate().powZn(hash_to_Zq(c11.toBytes()))).mul(h2.duplicate().powZn(hash_value)).mul(h3) );

        Element temp3 = pairing.pairing(g, rki4);
        Element hash_value2 = hash_to_Zq(a_b(a_b(rki1.toBytes(),rki2.toBytes()),rki3.toBytes()));
        Element temp4 = pairing.pairing(rki1,(h2.duplicate().powZn(hash_value2)).mul(h4));

        List<Element> Cj = new ArrayList<>();

        System.out.println("temp1:");
        System.out.println(temp1);
        System.out.println("temp2:");
        System.out.println(temp2);
        System.out.println("temp3:");
        System.out.println(temp3);
        System.out.println("temp4:");
        System.out.println(temp4);



        if((temp1.isEqual(temp2)) && (temp3.isEqual(temp4))){
            Element c111 = c11;
            Element c121 = c12.duplicate().mul(pairing.pairing(c11,rki2));
            Element c131 = c13;
            Element c211 = rki1;
            Element c221 = rki1.duplicate().powZn(skpi);
            Element c231 = (h1.duplicate().powZn(hash_to_Zq(a_b(a_b(a_b(a_b(c111.toBytes(),c121.toBytes()),c131.toBytes()),c211.toBytes()),c221.toBytes()))).duplicate().mul(h2)).powZn(hash_to_Zq((rki3.duplicate().powZn(skpi)).toBytes()));
            Cj.add(c111);
            Cj.add(c121);
            Cj.add(c131);
            Cj.add(c211);
            Cj.add(c221);
            Cj.add(c231);
        } else{
            System.out.println("error");

        }
        return Cj;
    }

    public Element decrypt(Element skj,List<Element>  Cj){
        Element c12 = Cj.get(1);
        System.out.println("c12:");
        System.out.println(new String(c12.toBytes()));
        Element c11 = Cj.get(0);
        System.out.println("c11:");
        System.out.println(new String(c11.toBytes()));
        Element c21 = Cj.get(3);
        System.out.println("c21:");
        System.out.println(new String(c21.toBytes()));
        Element m;
        m = c12.duplicate().div((pairing.pairing(c11,c21).powZn(skj)));
        return m;
    }


    public static void main(String[] args) throws NoSuchAlgorithmException {
        PRE pre = new PRE();
        Long setupBeginTime =    System.currentTimeMillis() ;
        pre.setup();
        Long setupEndTime =    System.currentTimeMillis() ;
        Long setupTime = setupEndTime - setupBeginTime;
        System.out.println("setupTime:"+setupTime);

        Map<String,Element> user1 = new HashMap<>();
        Map<String,Element> user2 = new HashMap<>();
        Map<String,Element> proxy = new HashMap<>();

        Long keyGenBeginTime =    System.currentTimeMillis() ;
        user1 = pre.keyGen();
        Long keyGenEndTime =    System.currentTimeMillis() ;
        Long keyGenTime = keyGenEndTime - keyGenBeginTime;
        System.out.println("keyGenTime:"+keyGenTime);
        user2 = pre.keyGen();
        proxy = pre.keyGen();

        Element pk_user1 = user1.get("pk");
        Element sk_user1 = user1.get("sk");
        Element pk_user2 =  user2.get("pk");
        Element sk_user2 =  user2.get("sk");
        Element pk_proxy = proxy.get("pk");
        Element sk_proxy = proxy.get("sk");

        String message = "hello world1 The experimental environment involves Visual Studio C++\n" +
                "2012 IDE, open source library gmp 5.1.0 for big number\n" +
                "computation, openssl 0.9.8e for cryptography algorithm and\n" +
                "pbc 0.5.14 for bilinear pairing computation. The spec of the\n" +
                "experimental computers is as follows: the host is a com\u0002puter with Inter (R) Core (TM) i5-4590 on 3.30GHz and\n" +
                "16.0GB memory, and operating system installed is windows 7\n" +
                "ultimate (x64). To ensure fairness of the comparison, the algo\u0002rithms to compute big number, to make symmetric encryption\n" +
                "and bilinear pairing computation and the related parameters\n" +
                "are the same for the two schemes, where 192 bits CFB\n" +
                "AES is used for symmetric encryption and sha1 is used for\n" +
                "hashing. In addition, what needs to be stated is, because\n" +
                "Chu et al.’s CPBRE scheme uses symmetric bilinear pairing\n" +
                "and the proposed scheme uses asymmetric bilinear pairing,\n" +
                "so the bilinear pairing parameters they used are difference.\n" +
                "That is, the former uses the ‘‘pbc’’ standard A parameters, and\n" +
                "the latter uses the ‘‘pbc’’ standard D159 parameters instead.";
        Element message_GT = pre.GT.newElementFromBytes(message.getBytes());
//        Element message_GT = pre.GT.newRandomElement();
        System.out.println("orgin m:"+message_GT);

        Long encryptBeginTime =    System.currentTimeMillis() ;
        List<Element> ciphertext = pre.encrypt(pk_user1,message_GT);
        Long encryptEndTime =    System.currentTimeMillis() ;
        Long encryptTime = encryptEndTime - encryptBeginTime;
        System.out.println("encryptTime:"+encryptTime);


        Long rkgenBeginTime =    System.currentTimeMillis() ;
        List<Element> rk_i_j = pre.rkgen(sk_user1,pk_user2);
        Long rkgenEndTime =    System.currentTimeMillis() ;
        Long rkgenTime = rkgenEndTime - rkgenBeginTime;
        System.out.println("rkgenTime:"+rkgenTime);


        Long reEncryptBeginTime =    System.currentTimeMillis() ;
        List<Element> ciphertexte_j = pre.reEncrypt(rk_i_j,ciphertext,sk_proxy);
        Long reEncryptEndTime =    System.currentTimeMillis() ;
        Long reEncryptTime = reEncryptEndTime - reEncryptBeginTime;
        System.out.println("reEncryptTime:"+reEncryptTime);


        Long decryptBeginTime =    System.currentTimeMillis() ;
        Element m = pre.decrypt(sk_user2,ciphertexte_j);
        Long decryptEndTime =    System.currentTimeMillis() ;
        Long decryptTime = decryptEndTime - decryptBeginTime;
        System.out.println("decryptTime:"+decryptTime);

        System.out.println("decrypt m:"+m);

        byte[] result = new byte[m.getLengthInBytes()];
        System.out.println("m.length:"+m.getLengthInBytes());
        System.out.println("m_string.length:"+m.toBytes().length);

        String m_string = new String(m.toBytes());
        System.out.println(m_string);





    }

}
